package com.jumper.property.comparer.core.setting;

import com.jumper.property.comparer.core.annotations.DiffKey;
import com.jumper.property.comparer.core.annotations.UnionDisplayKey;
import com.jumper.property.comparer.core.annotations.UnionKey;
import com.jumper.property.comparer.core.bean.ObjectKeySetting;
import com.jumper.property.comparer.core.constant.DiffConstant;
import com.jumper.property.comparer.core.bean.CombinationPropertySetting;
import com.jumper.property.comparer.core.bean.PropertySetting;
import com.jumper.property.comparer.core.bean.RootSetting;
import com.jumper.property.comparer.core.diff.value.PrimitiveWrapper;
import com.jumper.property.comparer.core.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 设置解析默认实现
 */
public class SettingResolveImpl implements SettingResolver {

    private final Map<Class<?>, RootSetting> settingCache = new HashMap<>();
    private final Map<Class<?>, List<Field>> fieldCache = new HashMap<>();

    @Override
    public RootSetting resolve(Class<?> cls) {
        assert cls != null;
        RootSetting setting = settingCache.get(cls);
        if (setting != null) {
            return setting;
        }
        ObjectKeySetting objectKey = new ObjectKeySetting();
        List<PropertySetting> properties = new ArrayList<>();
        List<Field> fields = getAllFieldsList(cls);
        fields.forEach(field -> {
            field.setAccessible(true);
            DiffKey property = field.getAnnotation(DiffKey.class);
            if (property != null) {
                properties.add(resolve(field, property));
            }
            UnionDisplayKey unionDisplayKey = field.getAnnotation(UnionDisplayKey.class);
            if (unionDisplayKey != null) {
                objectKey.getDisplayFields().add(field);
            }
        });
        setting = getRoot(cls, properties, objectKey);
        settingCache.put(cls, setting);
        return setting;
    }

    /**
     * 构建根节点设置
     */
    private RootSetting getRoot(Class<?> rootCls, List<PropertySetting> propertySettings, ObjectKeySetting objectKey) {
        // 构建对象
        RootSetting rootSetting = new RootSetting();
        rootSetting.setCls(rootCls);
        rootSetting.setObjectKey(objectKey);
        rootSetting.setPropertySettings(filterProperties(propertySettings, DiffConstant.NORMAL_PROPERTY));
        rootSetting.setCombinationSettings(convertToCombinationSettings(filterProperties(propertySettings, DiffConstant.COMBINATION_PROPERTY)));
        rootSetting.setArraySettings(filterProperties(propertySettings, DiffConstant.ARRAY_PROPERTY));
        rootSetting.setObjectSettings(filterProperties(propertySettings, DiffConstant.OBJECT_PROPERTY));
        return rootSetting;
    }

    /**
     * 过滤指定类型属性
     */
    private List<PropertySetting> filterProperties(List<PropertySetting> propertySettings, int type) {
        return propertySettings.stream()
                .filter(e -> e.getType() == type)
                .collect(Collectors.toList());
    }

    /**
     * 出组属性
     */
    private List<CombinationPropertySetting> convertToCombinationSettings(List<PropertySetting> combinationProperties) {
        Map<String, List<PropertySetting>> combinationMap = combinationProperties.stream()
                .collect(Collectors.groupingBy(PropertySetting::getCombinationName));
        List<CombinationPropertySetting> groupSettings = new ArrayList<>();
        combinationMap.forEach((combinationName, value) -> {
            CombinationPropertySetting groupSetting = new CombinationPropertySetting();
            groupSetting.setCombinationName(combinationName);
            groupSetting.setCombinationProperties(value);
            groupSettings.add(groupSetting);
        });
        return groupSettings;
    }

    /**
     * 解析单个属性
     */
    private PropertySetting resolve(Field field, DiffKey property) {
        String propertyName = StringUtils.isEmpty(property.name()) ? field.getName() : property.name();
        int type = property.type();
        String combinationName = StringUtils.isEmpty(property.combinationName()) ? field.getName() : property.combinationName();
        Class<?> subCls = property.subCls();
        boolean fullProps = property.whenArrayFullProperties();

        PropertySetting setting = new PropertySetting();
        setting.setPropertyName(propertyName);
        setting.setType(type);
        setting.setCombinationName(combinationName);
        setting.setField(field);
        setting.setWhenArrayFullProperties(fullProps);

        boolean isObj = type == DiffConstant.OBJECT_PROPERTY;
        boolean isArr = type == DiffConstant.ARRAY_PROPERTY;
        if (isArr || isObj) {
            List<Field> fields = new ArrayList<>();
            if (isObj) {
                setting.setObjectSetting(resolve(field.getType()));
                fields = getAllFieldsList(field.getType());
            }
            if (isArr) {
                subCls = subCls == Void.class ? resolveGenericCls(field) : subCls;
                subCls = PrimitiveWrapper.isPrimitive(subCls) ? PrimitiveWrapper.class : subCls;
                setting.setObjectSetting(resolve(subCls));
                fields = getAllFieldsList(subCls);
            }
            setting.setKeyProperties(filterKeys(fields, UnionKey.class));
            setting.setDisplayProperties(filterKeys(fields, UnionDisplayKey.class));
        }
        return setting;
    }

    /**
     * 过滤标识
     */
    private List<String> filterKeys(List<Field> fields, Class<? extends Annotation> cls) {
        List<String> keys = fields.stream().filter(e -> e.isAnnotationPresent(cls)).map(Field::getName).distinct().collect(Collectors.toList());
        if (keys.isEmpty() && !cls.isAssignableFrom(DiffKey.class)) {
            keys = filterKeys(fields, DiffKey.class);
        }
        return keys;
    }

    /**
     * 获取泛型类型
     */
    private Class<?> resolveGenericCls(Field field) {
        Class<?> subCls;
        ParameterizedType type = (ParameterizedType) field.getGenericType();
        Type[] arguments = type.getActualTypeArguments();
        try {
            subCls = arguments.length == 0
                    ? Object.class
                    : Class.forName(arguments[0].getTypeName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("无效列表类型");
        }
        return subCls;
    }

    /**
     * 获取实例所有字段
     */
    public List<Field> getAllFieldsList(final Class<?> cls) {
        if (fieldCache.get(cls) != null) {
            return fieldCache.get(cls);
        }
        final List<Field> allFields = new ArrayList<>();
        Class<?> currentClass = cls;
        while (currentClass != null) {
            final Field[] declaredFields = currentClass.getDeclaredFields();
            allFields.addAll(Arrays.asList(declaredFields));
            currentClass = currentClass.getSuperclass();
        }
        fieldCache.put(cls, allFields);
        return allFields;
    }
}
